#include "xm_crash.h"

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <signal.h> // SIGINT
#include <time.h>
#include <string>
#include <string.h>

#include "xm_version.h"

static FILE * g_log_fp = NULL;

#define dbgv(...) do{  if(!g_log_fp) break; fprintf(g_log_fp, "<crash>[D] " __VA_ARGS__); fprintf(g_log_fp, "\n"); fflush(g_log_fp); }while(0)
#define dbgi(...) do{  if(!g_log_fp) break; fprintf(g_log_fp, "<crash>[I] " __VA_ARGS__); fprintf(g_log_fp, "\n"); fflush(g_log_fp); }while(0)
#define dbge(...) do{  if(!g_log_fp) break; fprintf(g_log_fp, "<crash>[E] " __VA_ARGS__); fprintf(g_log_fp, "\n"); fflush(g_log_fp); }while(0)

#define MAX_STACK_FRAMES 128

static char icky_global_program_name[1024];
static char icky_global_program_path[1024];

static
void open_stack_log(const char * exeName){
    if(g_log_fp){
        return;
    }
    
    time_t nowtime;
    struct tm *timeinfo;
    nowtime = time(NULL);
    timeinfo = localtime(&nowtime);

    char filename[1024];
    if(exeName[0] == '/'){
        sprintf(filename, "%s.%d.%04d%02d%02d-%02d%02d%02d.crash",
                exeName,
                getpid(),
                timeinfo->tm_year+1900,
                timeinfo->tm_mon+1,
                timeinfo->tm_mday,
                timeinfo->tm_hour,
                timeinfo->tm_min,
                timeinfo->tm_sec
        );
    }else{
        char * crashPath = getenv("XSWITCH_CRASH_LOG_ROOT");
        sprintf(filename, "%s/%s.%d.%04d%02d%02d-%02d%02d%02d.crash",
                crashPath ? crashPath : "/data/logs",
                exeName,
                getpid(),
                timeinfo->tm_year+1900,
                timeinfo->tm_mon+1,
                timeinfo->tm_mday,
                timeinfo->tm_hour,
                timeinfo->tm_min,
                timeinfo->tm_sec
        );
    }

    g_log_fp = fopen(filename, "wb");
    if(!g_log_fp) {
        fprintf(stderr, "App(pid:%d %s) crash. fail to open crash file %s\n", getpid(), icky_global_program_name, filename);
        return;
    }
    printf("successfully open crash file %s\n", filename);
    fprintf(stderr, "App(pid:%d %s) crash. crash file %s\n", getpid(), icky_global_program_name, filename);
}

static
void close_stack_log(){
    if(g_log_fp){
        fclose(g_log_fp);
        g_log_fp = NULL;
        printf("closed crash file\n");
    }
}

static
void print_call_stack()
{
    void* callstack[MAX_STACK_FRAMES];
    int i, frames = backtrace(callstack, MAX_STACK_FRAMES);
    dbgi("App(%s) callstack size %d", icky_global_program_path, frames);

    char** strs = backtrace_symbols(callstack, frames);
    if (strs == NULL) {
        dbgi("[%s] backtrace: cannot get backtrace symbols", icky_global_program_name);
        return;
    }

    for (i = 0; i < frames; ++i) {
        dbgi("[%s] callstack %03d : %s", icky_global_program_name, frames - i, strs[i]);
    }
    dbgi("!!!! Get line number cmd: (addr2line -Cif -e %s $ADDRESS) !!!!", icky_global_program_path);


    free(strs);
}

static int getResultFromSystemCall(const char* pCmd, char* pResult, int size)
{
    int fd[2];
    if(pipe(fd))   {
        dbgi("[%s] pipe error!", icky_global_program_name);
        return -1;
    }
    
    //prevent content in stdout affect result
    fflush(stdout);
    
    //hide stdout
    int bak_fd = dup(STDOUT_FILENO);
    int new_fd = dup2(fd[1], STDOUT_FILENO);
    
    //the output of `pCmd` is write into fd[1]
    int ret = system(pCmd);
    int bytes = read(fd[0], pResult, size-1);
    if(bytes < 0){
        dbgi("[%s] read cmd result error!", icky_global_program_name);
        return -1;
    }else {
        // remove \r \n
        while( bytes > 0){
            char c = pResult[bytes-1];
            if(c == '\r' || c == '\n'){
                bytes--;
            }else{
                break;
            }
        }
    }
    pResult[bytes] = 0;
    
    //resume stdout
    dup2(bak_fd, new_fd);
    
    return ret;
}


static int addr2line(char const * const program_name, void const * const addr)
{
    char addr2line_cmd[2048] = {0};
    
    /* have addr2line map the address to the relent line in the code */
#ifdef __APPLE__
    /* apple does things differently... */
    sprintf(addr2line_cmd,"atos -o %s %p", program_name, addr);
#else
    sprintf(addr2line_cmd,"addr2line -f -p -e %s %p", program_name, addr);
#endif
    
    /* This will print a nicely formatted string specifying the
     function and source line of the address */
    //    printf("exec: %s\n", addr2line_cmd);
    //    return system(addr2line_cmd);

    //dbgi("[%s] cmd:(%s)", icky_global_program_name, addr2line_cmd);

    static char msg[2048];
    int ret = getResultFromSystemCall(addr2line_cmd, msg, 2048);
    dbgi("[%s](%p) >>> %s", icky_global_program_name, addr, msg);
    return ret;
}

static void posix_print_stack_trace()
{
    if( (access(icky_global_program_path, 0 )) == -1){
        dbgi("[%s] not found: %s", icky_global_program_name, icky_global_program_path);
        return;
    }

    void *stack_traces[MAX_STACK_FRAMES];
    int i, trace_size = 0;
    
    trace_size = backtrace(stack_traces, MAX_STACK_FRAMES);
    dbgi("App(%s) trace_size : %d", icky_global_program_path, trace_size);

    char **messages = backtrace_symbols(stack_traces, trace_size);
    
    /* skip the first couple stack frames (as they are this function and
     our handler) and also skip the last frame as it's (always?) junk. */
    // for (i = 3; i < (trace_size - 1); ++i)
    // we'll use this for now so you can see what's going on
    for (i = 0; i < trace_size; ++i)
    {
        if (addr2line(icky_global_program_path, stack_traces[i]) != 0)
        {
            dbgi("[%s] error determining line # for: %s", icky_global_program_name, messages[i]);
        }
        
    }
    if (messages) { free(messages); }
}

static void SignalHandle(int sig){
    open_stack_log(icky_global_program_name);
    dbgi("============================");
    dbgi("pid=%d, signal = %d", getpid(), sig);
    dbgi("APP_VER = %s-%s", APP_VER_STR, GIT_DESCRIBE_STR);
    //dbgi("GIT_VER = %s", GIT_DESCRIBE_STR);
    // print_call_stack();
    dbgi("-------");
    // posix_print_stack_trace();
    close_stack_log();
    exit(1);
}

void xm_crash_init(int argc, char **argv){
    if(argv[0][0] == '/') {
        sprintf(icky_global_program_path, "%s", argv[0]);
    }else{
        char * exeRoot = getenv("XSWITCH_EXE_FILE_ROOT");
        sprintf(icky_global_program_path, "%s/%s",
                exeRoot ? exeRoot : "/app/bin",
                argv[0]
        );
    }

    char *p;
    strcpy(icky_global_program_name, (p=strrchr(icky_global_program_path,'/')) ? p+1 : icky_global_program_path);
    printf("App(pid:%d %s) xm_crash_init. it is [%s]\n",  getpid(), icky_global_program_name, icky_global_program_path);

    signal(SIGSEGV, SignalHandle);
    signal(SIGABRT, SignalHandle);
}


